// Copyright (c) 2025 Inochi Amaoto

/* SPDX-License-Identifier: GPL-2.0 */

#ifndef __VHOST_VAPP_H__
#define __VHOST_VAPP_H__

#define VHOST_VAPP

#include <linux/vhost.h>
#include <linux/ioctl.h>
#include <linux/miscdevice.h>
#include <linux/hashtable.h>
#include <linux/wait.h>
#include <linux/spinlock.h>
#include <linux/list.h>
#include <linux/virtio_vapp.h>
#include "vhost.h"
#include "vapp-common.h"

enum {
  VHOST_VAPP_FEATURES = VHOST_FEATURES
};

enum {
  VHOST_VAPP_BACKEND_FEATURES = (1 << VHOST_BACKEND_F_IOTLB_MSG_V2)
};

#define VHOST_VAPP_DEVICE_MINOR MISC_DYNAMIC_MINOR
#define VHOST_VAPP_DEVICE_MODE (umode_t)(S_IRUGO | S_IWUGO)
#define VHOST_VAPP_PKT_WEIGHT 256

static inline long inline vhost_vapp_get_features(unsigned long arg)
{
  u64 features = VHOST_VAPP_FEATURES;

  if (copy_to_user((void*)arg, &features, sizeof(features)))
    return -EFAULT;

  return 0;
}

static inline long inline vhost_vapp_get_backend_features(unsigned long arg)
{
  u64 features = VHOST_VAPP_BACKEND_FEATURES;

  if (copy_to_user((void*)arg, &features, sizeof(features)))
    return -EFAULT;

  return 0;
}

/**
 * Backend vio driver
 */
int __init vio_vapp_init(void);
void __exit vio_vapp_exit(void);

struct vio_vapp_notify_entity {
  struct list_head node;
  u64 uuid;
  u64 id;
  u32 type;
};

struct vio_vapp_notify_queue {
  struct list_head queue;
  wait_queue_head_t wait;
  spinlock_t lock;
};

static inline struct vio_vapp_notify_entity* vio_vapp_notify_entity_alloc(u64 uuid, u64 id, u32 type)
{
  struct vio_vapp_notify_entity* notify = kmalloc(sizeof(*notify), GFP_KERNEL);
  if (!notify)
    return NULL;

  INIT_LIST_HEAD(&notify->node);
  notify->uuid = uuid;
  notify->id = id;
  notify->type = type;

  return notify;
}


static inline void vio_vapp_notify_entity_free(struct vio_vapp_notify_entity* notify)
{
  kvfree(notify);
}

static inline void vio_vapp_notify_queue_init(
struct vio_vapp_notify_queue* queue)
{
  INIT_LIST_HEAD(&queue->queue);
  init_waitqueue_head(&queue->wait);
  spin_lock_init(&queue->lock);
}

static inline void vio_vapp_notify_queue_destroy(
struct vio_vapp_notify_queue* queue)
{
  unsigned long flags;
  struct vio_vapp_notify_entity* n,* pos;
  spin_lock_irqsave(&queue->lock, flags);

  list_for_each_entry_safe(pos, n, &queue->queue, node) {
    list_del(&pos->node);
    vio_vapp_notify_entity_free(pos);
  }

  spin_unlock_irqrestore(&queue->lock, flags);
}

static inline void vio_vapp_notify_queue_recovery_mount(
struct vio_vapp_notify_queue* queue, struct vio_vapp_notify_entity* notify)
{
  unsigned long flags;
  spin_lock_irqsave(&queue->lock, flags);
  list_add(&notify->node, &queue->queue);
  spin_unlock_irqrestore(&queue->lock, flags);

  wake_up(&queue->wait);
}


static inline void vio_vapp_notify_queue_mount(
struct vio_vapp_notify_queue* queue, struct vio_vapp_notify_entity* notify)
{
  unsigned long flags;
  spin_lock_irqsave(&queue->lock, flags);
  list_add_tail(&notify->node, &queue->queue);
  spin_unlock_irqrestore(&queue->lock, flags);

  wake_up(&queue->wait);
}


static inline struct vio_vapp_notify_entity* vio_vapp_notify_queue_get(
struct vio_vapp_notify_queue* queue)
{
  struct vio_vapp_notify_entity* notify = NULL;
  unsigned long flags;

  spin_lock_irqsave(&queue->lock, flags);
  if (!list_empty(&queue->queue)) {
    notify = list_first_entry(&queue->queue, struct vio_vapp_notify_entity, node);
    list_del(&notify->node);
  }
  spin_unlock_irqrestore(&queue->lock, flags);

  return notify;
}

static inline struct vio_vapp_notify_entity* vio_vapp_notify_queue_wait(
struct vio_vapp_notify_queue* queue)
{
  struct vio_vapp_notify_entity* notify;
  unsigned long flags;
  int ret;

  ret = wait_event_interruptible(queue->wait, (notify = vio_vapp_notify_queue_get(queue)));
  if (ret) {
    pr_warn("wait interruptted\n");
    return ERR_PTR(ret);
  }

  spin_lock_irqsave(&queue->lock, flags);
  spin_unlock_irqrestore(&queue->lock, flags);

  return notify;
}

struct vio_vapp_context {
  struct vio_vapp_notify_queue queue;
  struct vio_mmap_entity_tree mtree;
};

struct vio_vapp_context* vhost_vapp_get_vio_context(void);

/**
 * vhost driver
 */
struct vhost_vapp_dev;

struct vhost_vapp_dtq {
  struct vhost_virtqueue vqs[2];
  struct vhost_work work;

  struct work_struct rx_work;
  struct work_struct tx_work;
  struct work_struct send_work;

  struct vapp_send_queue* queue;

  struct vhost_vapp_dev* vadev;

  atomic_t queued_replies;
  atomic_t id_pool;
};

struct vhost_vapp_dev {
  struct hlist_node node;
  struct vhost_dev dev;
  struct virtio_vapp_cpu_config conf;

  struct vapp_send_queue cqueue;
  struct vhost_vapp_dtq cdtq;

  struct vapp_send_queue dqueue;
  struct vhost_vapp_dtq* ddtqs;

  struct vio_vapp_entity_tree req;
  struct vio_vapp_entity_tree rsp;
};

struct vhost_vapp_dev* vhost_vapp_get_dev(u64 uuid);

static inline struct vio_vapp_entity_tree* vhost_vapp_select_tree(struct vhost_vapp_dev* vapp, u32 type)
{
  if (type & VIO_TYPE_GUEST)
    return &vapp->req;
  else
    return &vapp->rsp;
}

static inline struct vio_mmap_entity_tree* vio_mmap_entity_tree_from_file(struct file* fp)
{
  struct vio_vapp_context* ctx = fp->private_data;
  return &ctx->mtree;
}

#endif // ! __VHOST_VAPP_H__
